package net.yeah.mickey_zhouyou.database.util;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import net.yeah.mickey_zhouyou.database.exception.NoFieldDeclaredException;

public class ClassFieldsCache {

	private static Map<Class<?>, Cache> mapClassToCache = new ConcurrentHashMap<Class<?>, Cache>();

	/**
	 * 返回此类的所有非静态属性。
	 * 
	 * @param <T>
	 * @param clazz
	 * @return
	 */
	public static <T> Set<Entry<String, Field>> getAllFields(
			Class<? extends T> clazz) {
		Cache cache = parseClass(clazz);
		return cache.allFields();
	}

	public static <T> Object getValue(T obj, String fieldName) {
		Field field = getField(obj.getClass(), fieldName);
		try {
			return field.get(obj);
		} catch (IllegalAccessException e) {
			/**
			 * 在 static Cache parseClass(Class<?> clazz)中,
			 * 已经有运行field.setAccessible(true); 所以应该不会运行到这里。
			 */
			e.printStackTrace();
			return null;
		}
	}

	public static <T, E> void setValue(T obj, String fieldName, E value) {
		Field field = getField(obj.getClass(), fieldName);
		try {
			field.set(obj, value);
		} catch (IllegalAccessException e) {
			/**
			 * 在 static Cache parseClass(Class<?> clazz)中,
			 * 已经有运行field.setAccessible(true); 所以应该不会运行到这里。
			 */
			e.printStackTrace();
		}
	}

	private static Field getField(Class<?> clazz, String fieldName) {
		Cache cache = parseClass(clazz);
		Field field = cache.getField(fieldName);
		if (field == null)
			throw new NoFieldDeclaredException();
		return field;
	}

	private static Cache parseClass(Class<?> clazz) {
		if (clazz == null)
			throw new NullPointerException("参数clazz不能为空");

		Cache cache = mapClassToCache.get(clazz);
		if (cache != null)
			return cache;

		synchronized (clazz) {
			if (mapClassToCache.get(clazz) == null) {
				cache = new Cache();
				Field[] fields = clazz.getDeclaredFields();
				for (Field field : fields) {
					field.setAccessible(true);
					cache.addField(field);
				}
				Class<?> superClass = clazz.getSuperclass();
				if (superClass != null)
					cache.setSuper(parseClass(clazz.getSuperclass()));
				mapClassToCache.put(clazz, cache);
			}
		}
		return cache;
	}

}

class Cache {

	private Map<String, Field> fieldsMap;

	Cache() {
		fieldsMap = new HashMap<String, Field>();
	}

	Field getField(String fieldName) {
		return fieldsMap.get(fieldName);
	}

	Set<Entry<String, Field>> allFields() {
		return MapUtil.unmodifiableEntrySet(fieldsMap.entrySet());
	}

	void addField(Field field) {
		this.fieldsMap.put(field.getName(), field);
	}

	void setSuper(Cache superCache) {
		if (superCache != null) {
			fieldsMap.putAll(superCache.fieldsMap);
		}
	}
}